
#include "DielectricMaterial.h"
#include "HitRecord.h"
#include "Light.h"
#include "Point.h"
#include "Primitive.h"
#include "Ray.h"
#include "RenderContext.h"
#include "Scene.h"
#include "Vector.h"
#include "Math.h"
#include "Background.h"
#include <iostream>
using namespace std;

void handlePhongPortion(Color& result, const RenderContext& context,
        const Ray& ray, const Point& hitpos, const Vector& normal, double exponent);
	

DielectricMaterial::DielectricMaterial(float eta, float exponent)
  :eta(eta), exponent(exponent)
{
	this->inv_eta = 1.0/eta;
	this->r0 = (eta - 1)/(eta + 1);
	this->r0 = r0 * r0;
}

DielectricMaterial::~DielectricMaterial()
{
}

void DielectricMaterial::shade(Color& result, const RenderContext& context,
                               const Ray& ray, const HitRecord& hit, const Color& atten, int depth) const
{
	//check if we are at the end of the recursion
	const Scene* scene = context.getScene();
	if (depth >= scene->getMaxRayDepth() || atten.maxComponent() <= scene->getMinAttenuation()) {
			result = Color(0, 0, 0);
			return;
		}

	//determine the angle between the ray and the normal for determining what type of
	//reflection occurs
	Point hitpos = ray.origin()+ray.direction()*hit.minT();
	Vector normal;
	hit.getPrimitive()->normal(normal, context, hitpos, ray, hit);
	Vector unflipped_normal = normal;
	
	double costheta = Dot(normal, ray.direction());
	bool exiting; //whether the ray is exiting the current primitive
	float eta_tmp;
	if(costheta > 0)
	{
		// Exiting surface
		normal = -normal;
		//eta_tmp = eta;
		eta_tmp = inv_eta;
		exiting = true;
	} else {
		// Entering surface
		//eta_tmp = inv_eta;
		eta_tmp = eta;
		costheta = -1 * costheta;
		exiting = false;
	}

	//handle phong effect
	Color phong_effect(0, 0, 0);
	Color white(1, 1, 1);
	handlePhongPortion(phong_effect, context, ray, hitpos, unflipped_normal, this->exponent);
	result = phong_effect*white;
	
	Vector reflection_dir = ray.direction() + ((2 * costheta) * normal);
	reflection_dir.normalize();
	double costheta2squared = 1 + (costheta*costheta-1)/(eta_tmp*eta_tmp);
	const Object* world = scene->getObject();
	if(costheta2squared < 0)
	{
		// Total internal reflection...
		Ray reflected_ray(hitpos, reflection_dir);
		double t_reflection;
		Color reflection_color( 0, 0, 0 );
		HitRecord reflectionHit( DBL_MAX );
		world->intersect( reflectionHit, context, reflected_ray );
		if( reflectionHit.getPrimitive() ) {
			// Ray hit something...
		    const Material* matl = reflectionHit.getMaterial();
		    matl->shade(reflection_color, context, reflected_ray, reflectionHit, atten, depth + 1);
		} else {
			if(depth + 1 < scene->getMaxRayDepth())
				scene->getBackground()->getBackgroundColor( reflection_color, context, reflected_ray );
		}

		t_reflection = reflectionHit.minT();
		result += reflection_color;
		//result = Color(1, 1, 1);
	} else { //no total internal reflection
		double costheta2 = sqrt(costheta2squared);
		double cosm = 1 - min(costheta, costheta2);
		double cosm2 = cosm*cosm;
		double cosm4 = cosm2*cosm2;
		double cosm5 = cosm4*cosm;
		double reflection_coefficient = r0*(1-cosm5)+cosm5;
		//double reflection_coefficient = r0+(1-r0)*cosm5;
		
		// Reflective ray
		Ray reflection_ray(hitpos, reflection_dir);
		Color reflection_color( 0, 0, 0 );
		HitRecord reflectionHit( DBL_MAX );
		world->intersect( reflectionHit, context, reflection_ray );
				
		double t_reflection = reflectionHit.minT();
		if( reflectionHit.getPrimitive() ) {
			// Ray hit something...
			const Material* matl = reflectionHit.getMaterial();
			matl->shade(reflection_color, context, reflection_ray, reflectionHit, atten * reflection_coefficient, depth + 1);
		} else {
			if(depth + 1 < scene->getMaxRayDepth())
				scene->getBackground()->getBackgroundColor( reflection_color, context, reflection_ray );
		}
		
		result += reflection_color * reflection_coefficient;
		
		
		
		// Transparency ray
		double transparency_coefficient = 1 - reflection_coefficient;
		double c1 = Dot(-normal, ray.direction());
		Vector transparency_direction = (ray.direction() * (1.0 / eta_tmp)) + normal * ( ( costheta / eta_tmp ) - costheta2 );
		transparency_direction.normalize();
		Color transparency_color( 0, 0, 0 );
		Ray transparency_ray(hitpos, transparency_direction);
		HitRecord transparencyHit( DBL_MAX );
		world->intersect( transparencyHit, context, transparency_ray );
				
		double t_transparency = transparencyHit.minT();
		if (transparencyHit.getPrimitive() ) {
			// Ray hit something...
			const Material* matl = transparencyHit.getMaterial();
			matl->shade(transparency_color, context, transparency_ray,
					transparencyHit, atten * transparency_coefficient, depth + 1);
		} else {
			if(depth + 1 < scene->getMaxRayDepth())
				scene->getBackground()->getBackgroundColor(transparency_color, context,
					transparency_ray );
		}
		result += transparency_color * transparency_coefficient;
		//cerr << "Reflection: " << reflection_coefficient << "\tTransparency: " << transparency_coefficient << endl;
	}
}

//Handle the phong portion of the dielectric effect
void handlePhongPortion(Color& result, const RenderContext& context,
		const Ray& ray, const Point& hitpos, const Vector& normal,
		double exponent) {

	const Scene* scene = context.getScene();
	const vector<Light*>& lights = scene->getLights();
	Light*const* begin = &lights[0];
	Light*const* end = &lights[0]+lights.size();
	while (begin != end) {
		Color light_color;
		Vector light_direction;
		double dist = (*begin++)->getLight(light_color, light_direction, context,
				hitpos);
		double cosphi = Dot(normal, light_direction);
		if (cosphi > 0) {
			//light is visible, now get the phong exponent
			Vector H = light_direction - ray.direction();
			H.normalize();
			double cos_alpha = Dot(H, normal);
			if (cos_alpha > 0) {
				double phong_term = pow(cos_alpha, exponent);
				result = result + light_color * phong_term;
			}
		}
	}
}